#include "stdafx.h"
#include "ThreadTestSuite.h"

namespace CO { namespace SpiFoundation { namespace RoomFW {

class TestThread : public CO::SpiFoundation::RoomFW::EventThread
{
public:
	TestThread(void);
	bool signal(DWORD timeout);
	bool passed(void);
	void reset(void);
protected:
	virtual bool initializeObject(void);
	virtual bool initializeThread(void);
	virtual bool waitEvent(void);
	virtual bool handleEvent(void);
	virtual void signalStop(void);
	virtual void finalize(void);
private:
	CO::SpiFoundation::RoomFW::SimpleEvent stopEvent_;
	CO::SpiFoundation::RoomFW::SimpleEvent testEvent_;
	CO::SpiFoundation::RoomFW::SimpleEvent testEvent2_;
	CO::SpiFoundation::RoomFW::SimpleCriticalSection cs_;
	bool initializeObj_;
	bool initializeThread_;
	bool waitEvent_;
	bool handleEvent_;
	bool signalStop_;
	bool finalize_;
};
TestThread::TestThread(void)
{
	this->reset();
}
bool TestThread::signal(DWORD timeout)
{
	testEvent_.set();
	return testEvent2_.wait(timeout);
}
bool TestThread::passed(void)
{
	ASSERT(initializeObj_);
	ASSERT(initializeThread_);
	ASSERT(waitEvent_);
	ASSERT(handleEvent_);
	ASSERT(signalStop_);
	ASSERT(finalize_);
	return (initializeObj_ &&
			initializeThread_ &&
			waitEvent_ &&
			handleEvent_ &&
			signalStop_ &&
			finalize_);
}
void TestThread::reset(void)
{
	initializeObj_ = false;
	initializeThread_ = false;
	waitEvent_ = false;
	handleEvent_ = false;
	signalStop_ = false;
	finalize_ = false;
}
bool TestThread::initializeObject(void)
{
	if (stopEvent_.create() && testEvent_.create() && testEvent2_.create())
	{
		CO::SpiFoundation::RoomFW::CriticalSectionGuard g(cs_);
		initializeObj_ = true;
	}
	return initializeObj_;
}
bool TestThread::initializeThread(void)
{
	//if (initializeObj_)
	{
		CO::SpiFoundation::RoomFW::CriticalSectionGuard g(cs_);
		initializeThread_ = true;
	}
	return true;
}
bool TestThread::waitEvent(void)
{
	const HANDLE waitObjs[] = {stopEvent_, testEvent_};
	const size_t waitObjsSize = sizeof(waitObjs) / sizeof(HANDLE);
	DWORD ret = ::WaitForMultipleObjects(waitObjsSize, waitObjs, false, INFINITE);
	SHOW_LAST_ERROR((WAIT_FAILED != ret), "ActiveObject::waitEvent", "::WaitForMultipleObjects");
	//if (initializeThread_)
	{
		CO::SpiFoundation::RoomFW::CriticalSectionGuard g(cs_);
		if ((WAIT_OBJECT_0 + 1) == ret)
		{
			waitEvent_ = true;
		}
	}
	return ((WAIT_OBJECT_0 + 1) == ret);
}
bool TestThread::handleEvent(void)
{
	//if (waitEvent_)
	{
		CO::SpiFoundation::RoomFW::CriticalSectionGuard g(cs_);
		handleEvent_ = true;
		testEvent2_.set();
	}
	return true;
}
void TestThread::signalStop(void)
{
	if (stopEvent_.set() /*&& handleEvent_*/)
	{
		CO::SpiFoundation::RoomFW::CriticalSectionGuard g(cs_);
		signalStop_ = true;
	}
}
void TestThread::finalize(void)
{
	//if (signalStop_)
	{
		CO::SpiFoundation::RoomFW::CriticalSectionGuard g(cs_);
		finalize_ = true;
	}
}

int operator-(const _timeb& a, const _timeb& b)
{
	return (((a.time - b.time) * 1000) +
			(static_cast<int>(a.millitm) - static_cast<int>(b.millitm)));
}

class BusyThread : public CO::SpiFoundation::RoomFW::Thread
{
public:
	static size_t MaxCount;
	static CO::SpiFoundation::RoomFW::SimpleEvent StartEvent;
	static CO::SpiFoundation::RoomFW::SimpleEvent StopEvent;

	~BusyThread()
	{
		VERIFY(this->stop());
	}

	size_t count()
	{
		return count_;
	};

protected:

	virtual void main(void)
	{
		count_ = 0;
		if (StartEvent.wait())
		{
			while (!StopEvent.wait(0))
			{
				if (++count_ >= MaxCount)
				{
					VERIFY(StopEvent.set());
				}
			}
		}
	}

	virtual void signalStop(void)
	{
		VERIFY(StartEvent.set() && StopEvent.set());
	}

	size_t count_;
};

size_t BusyThread::MaxCount = 0;
CO::SpiFoundation::RoomFW::SimpleEvent BusyThread::StartEvent;
CO::SpiFoundation::RoomFW::SimpleEvent BusyThread::StopEvent;

ThreadTestSuite::ThreadTestSuite(void)
{
	LOG_TEST_ADD(ThreadTestSuite::all)
	//LOG_TEST_ADD(ThreadTestSuite::priority)
	//LOG_TEST_ADD(ThreadTestSuite::startTime)
	//LOG_TEST_ADD(ThreadTestSuite::logTime)
	//LOG_TEST_ADD(ThreadTestSuite::memoryHog)
}

ThreadTestSuite::~ThreadTestSuite(void)
{
}

void ThreadTestSuite::setup()
{
	LoggedTestSuite::setup();
}

void ThreadTestSuite::tear_down()
{
	LoggedTestSuite::tear_down();
}

void ThreadTestSuite::all(void)
{
	const DWORD wait = 2000;
	TestThread t;
	TEST_ASSERT(t.start(wait));
	TEST_ASSERT(t.signal(wait));
	TEST_ASSERT(t.stop(wait));
	TEST_ASSERT(t.passed());
}

void ThreadTestSuite::priority(void)
{
	const DWORD wait = 2000;

	// Get number of processors.
	size_t proCount = 0;
	DWORD_PTR procMask = 0;
	DWORD_PTR sysMask = 0;
	TEST_ASSERT(::GetProcessAffinityMask(::GetCurrentProcess(), &procMask, &sysMask));
	while (procMask & (1 << proCount++))
		;

	// Initialize.
	TEST_ASSERT(BusyThread::StartEvent.create(true));
	TEST_ASSERT(BusyThread::StopEvent.create(true));

	// Run a test thread to get a reasonable max count for test.
	BusyThread::MaxCount = UINT_MAX;
	BusyThread testThread;
	TEST_ASSERT(testThread.start(wait));
	TEST_ASSERT(BusyThread::StartEvent.set());
	if (!testThread.wait(wait))
	{
		TEST_ASSERT(BusyThread::StopEvent.set());
		TEST_ASSERT(testThread.wait(wait));
		BusyThread::MaxCount = testThread.count() / 10;
	}
	TEST_ASSERT(BusyThread::StopEvent.reset());
	TEST_ASSERT(BusyThread::StartEvent.reset());

	// Now set up the real test.
	const size_t ThreadCount = proCount; // Want one high priority thread per processor.
	boost::shared_array<BusyThread> low(new BusyThread[ThreadCount]);
	boost::shared_array<BusyThread> high(new BusyThread[ThreadCount]);
	for (size_t i = 0; i < ThreadCount; ++i)
	{
		TEST_ASSERT(low[i].start(wait, THREAD_PRIORITY_BELOW_NORMAL));
		TEST_ASSERT(low[i].isRunning(wait));
		TEST_ASSERT(high[i].start(wait, THREAD_PRIORITY_ABOVE_NORMAL));
		TEST_ASSERT(high[i].isRunning(wait));
		TEST_ASSERT(THREAD_PRIORITY_ERROR_RETURN != low[i].getThreadPriority());
		TEST_ASSERT(THREAD_PRIORITY_ERROR_RETURN != high[i].getThreadPriority());
		TEST_ASSERT(low[i].getThreadPriority() < high[i].getThreadPriority());
	}
	// Run the test.
	TEST_ASSERT(BusyThread::StartEvent.set());
	TEST_ASSERT(BusyThread::StopEvent.wait(wait));
	for (size_t i = 0; i < ThreadCount; ++i)
	{
		TEST_ASSERT(high[i].wait(wait));
		TEST_ASSERT(low[i].wait(wait));
	}
	// Higher priority should all get pretty close to max, lower priority should be close to 0.
	const size_t lowMark = BusyThread::MaxCount / 100;
	const size_t highMark = lowMark * 99;
	for (size_t i = 0; i < ThreadCount; ++i)
	{
		TEST_ASSERT(high[i].count() > highMark);
		TEST_ASSERT(low[i].count() < lowMark);
	}
}

class EmptyThread : public CO::SpiFoundation::RoomFW::Thread
{
protected:
	virtual void main() { VERIFY(stopEvent_.wait()); }
	bool initializeObject() { return stopEvent_.create(); }
	virtual void signalStop() { VERIFY(stopEvent_.set()); }
private:
	SimpleEvent stopEvent_;
};

void LogStartTimeResults(const char* methodName, std::ostream& results, size_t startCount, double testTime)
{
	results << "startCount=" << startCount
			<< ", milliseconds/" << methodName << "=" << ((testTime * 1000.0) / startCount) << std::endl;
}

void ControlFunction()
{
	double x = 1.0;
	const int y = 1000;
	for (int i = 0; i < y; ++i)
	{
		x *= 2;
	}
	std::cout << "2^" << y << "=" << x << std::endl;
}

void ThreadTestSuite::startTime(void)
{
	static const uint32_t MAX_WAIT = 2000;

	LARGE_INTEGER frequency = {0, 0};
	BOOL ret = ::QueryPerformanceFrequency(&frequency); // Counts per second
	SHOW_LAST_ERROR(ret, "ThreadTestSuite::startTime", "QueryPerformanceFrequency");
	TEST_ASSERT_MSG(ret, "QueryPerformanceFrequency");

	std::ofstream results("StartTimeResults.txt");
	TEST_ASSERT(results.is_open());

	EmptyThread emptyThread;
	size_t startCount = 1;
	size_t totalStarts = 0;
	double testTime = 0.0;
	double totalTime = 0.0;
	double testControlTime = 0.0;
	double totalControlTime = 0.0;
	do
	{
		LARGE_INTEGER startTicks = {0, 0};
		LARGE_INTEGER endTicks = {0, 0};
		LARGE_INTEGER testTicks = {0, 0};
		LARGE_INTEGER controlTicks = {0, 0};
		for (size_t i = 0; i < startCount; ++i)
		{
			TEST_ASSERT(::QueryPerformanceCounter(&startTicks));
			bool threadStarted = emptyThread.start(MAX_WAIT);
			TEST_ASSERT(::QueryPerformanceCounter(&endTicks));
			TEST_ASSERT(endTicks.QuadPart > startTicks.QuadPart);
			testTicks.QuadPart += (endTicks.QuadPart - startTicks.QuadPart);
			if (!threadStarted)
			{
				TEST_ASSERT(emptyThread.isErrored());
				TEST_ASSERT_MSG(false, emptyThread.errorMessage().c_str());
			}
			else
			{
				TEST_ASSERT(emptyThread.stop(MAX_WAIT));
			}
			TEST_ASSERT(::QueryPerformanceCounter(&startTicks));
			ControlFunction();
			TEST_ASSERT(::QueryPerformanceCounter(&endTicks));
			TEST_ASSERT(endTicks.QuadPart > startTicks.QuadPart);
			controlTicks.QuadPart += (endTicks.QuadPart - startTicks.QuadPart);
		}
		testTime = static_cast<double>(testTicks.QuadPart) / static_cast<double>(frequency.QuadPart);
		testControlTime = static_cast<double>(controlTicks.QuadPart) / static_cast<double>(frequency.QuadPart);
		LogStartTimeResults("thread", std::cout, startCount, testTime);
		LogStartTimeResults("thread", results, startCount, testTime);
		LogStartTimeResults("control", std::cout, startCount, testControlTime);
		LogStartTimeResults("control", results, startCount, testControlTime);
		totalTime += testTime;
		totalControlTime += testControlTime;
		totalStarts += startCount;
		startCount *= 2;
	} while ((testTime < 2) && (testControlTime < 2));
	LogStartTimeResults("thread", std::cout, totalStarts, totalTime);
	LogStartTimeResults("thread", results, totalStarts, totalTime);
	LogStartTimeResults("control", std::cout, totalStarts, totalControlTime);
	LogStartTimeResults("control", results, totalStarts, totalControlTime);
}

void ThreadTestSuite::logTime(void)
{
	for (size_t i = 0; i < 10000; ++i)
	{
		G_LOG_1_TRACE("Begin %i", i);
		G_LOG_1_TRACE("End %i", i);
	}
}

class MemoryThread : public CO::SpiFoundation::RoomFW::Thread
{
public:
	~MemoryThread()
	{
		VERIFY(this->stop());
	}
	std::vector<boost::shared_array<char> > arr_;
protected:
	virtual bool initializeObject()
	{
		return stopEvent_.create();
	}
	virtual void main(void)
	{
		while (!stopEvent_.wait(0))
		{
			arr_.push_back(boost::shared_array<char>(new char[0xFFFFFFF]));
		}
	}
	virtual void signalStop(void)
	{
		VERIFY(stopEvent_.set());
	}
	CO::SpiFoundation::RoomFW::SimpleEvent stopEvent_;
};

void ThreadTestSuite::memoryHog(void)
{
	MemoryThread t;
	TEST_ASSERT(t.start(DWait));
	TEST_ASSERT(t.wait(DWait));
	TEST_ASSERT(t.errorMessage() == "bad allocation");
}

}}}
